﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://wpfimageviewer.codeplex.com
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using BrainTechLLC.ThreadSafeObjects;

namespace BrainTechLLC
{
    /// <summary>
    /// Keeps basic statistics about a sequence of measurements / values
    /// </summary>
    [Serializable]
    public class Average : BaseNotifyPropertyChanged
    {
        public object _lockObject = new object();
        public ThreadSafeListValues<double> _values;
        protected double _sum;
        protected double _count;
        protected double _averageValue;
        protected bool _dirty;
        protected double _min = double.MaxValue;
        protected double _max = double.MinValue;

        public int TrimWhenCount = 100;
        public int NumberToTrim = 20;

        public Average()
        {
        }

        public Average(int trimWhenCount, int numberToTrim)
        {
            TrimWhenCount = trimWhenCount;
            NumberToTrim = numberToTrim;
        }

        protected void DoIfDirty(Action a)
        {
            if (_dirty)
            {
                lock (_lockObject)
                {
                    if (_dirty)
                    {
                        a();
                    }
                }
            }
        }

        public double Sum
        {
            get
            {
                RefreshIfDirty();
                return _sum;
            }
            protected set
            {
                if (_sum != value)
                {
                    _sum = value;
                }
            }
        }

        public double Min
        {
            get
            {
                RefreshIfDirty();
                return _min;
            }
            protected set
            {
                if (_min != value)
                {
                    _min = value;
                }
            }
        }

        public double Max
        {
            get
            {
                RefreshIfDirty();
                return _max;
            }
            protected set
            {
                if (_max != value)
                {
                    _max = value;
                }
            }
        }

        public double AverageValue
        {
            get
            {
                RefreshIfDirty();
                return _averageValue;
            }
            protected set
            {
                if (_averageValue != value)
                {
                    _averageValue = value;
                }
            }
        }

        public string AverageValueMSString
        {
            get
            {
                double val = AverageValue;
                return val.ToString("N0").Replace(",", ".") + "ms";
            }
        }

        public double Count
        {
            get
            {
                RefreshIfDirty();
                return _count;
            }
            protected set
            {
                if (_count != value)
                {
                    _count = value;
                }
            }
        }


        public bool Dirty
        {
            get { return _dirty; }
            set
            {
                if (_dirty != value)
                {
                    _dirty = value;
                }
            }
        }

        protected bool RefreshIfDirty()
        {
            bool wasDirty = false;

            if (_dirty)
            {
                lock (_lockObject)
                {
                    if (_dirty)
                    {
                        double min = double.MaxValue;
                        double max = double.MinValue;
                        double sum = 0D;
                        var all = Values.AllItems;

                        for (int n = 0; n < all.Count; n++)
                        {
                            double v = all[n];

                            if (v < min)
                            {
                                min = v;
                            }
                            if (v > max)
                            {
                                max = v;
                            }

                            sum += v;
                        }

                        _dirty = false;

                        Sum = sum;
                        Count = all.Count;

                        Min = min;
                        Max = max;

                        if (Count != 0D)
                        {
                            AverageValue = Sum / Count;
                        }
                        else
                        {
                            AverageValue = 0D;
                        }

                        wasDirty = true;
                    }
                }

                if (wasDirty)
                {
                    OnChanged(true, true, true, true, true);
                }
            }

            return wasDirty;
        }

        public void AddValue(double val)
        {
            RefreshIfDirty();
            bool newMax = false;
            bool newMin = false;
            bool wasDirty = false;

            lock (_lockObject)
            {
                _sum += val;
                _count++;
                if (val > _max)
                {
                    _max = val;
                    newMax = true;
                }
                if (val < _min)
                {
                    _min = val;
                    newMin = true;
                }
                _averageValue = _sum / _count;
                Values.Add(val);
            }            

            if (_count > TrimWhenCount)
            {
                RefreshIfDirty();

                lock (_lockObject)
                {
                    if (_count > TrimWhenCount)
                    {
                        for (int n = NumberToTrim - 1; n >= 0; n--)
                        {
                            Values.RemoveAt(n);
                        }

                        _dirty = true;
                    }
                }

                wasDirty = RefreshIfDirty();
            }

            if (!wasDirty)
                OnChanged(true, true, true, newMin, newMax);
        }

        public void OnChanged(bool avg, bool cnt, bool sum, bool min, bool max)
        {
            if (avg)
                OnPropertyChanged("AverageValue");
            if (cnt)
                OnPropertyChanged("Count");
            if (sum)
                OnPropertyChanged("Sum");
            if (min)
                OnPropertyChanged("Min");
            if (max)
                OnPropertyChanged("Max");
        }

        public ThreadSafeListValues<double> Values
        {
            get
            {
                if (_values == null)
                    Interlocked.CompareExchange<ThreadSafeListValues<double>>(ref _values, new ThreadSafeListValues<double>(), null);

                return _values;
            }
            set
            {
                if (_values != value)
                {
                    _values = value;
                    _dirty = true;
                    OnPropertyChanged("Values");
                }
            }
        }

        public void Clear()
        {
            lock (_lockObject)
            {
                _averageValue = 0D;
                _count = 0;
                _sum = 0;
                Interlocked.Exchange<ThreadSafeListValues<double>>(ref _values, new ThreadSafeListValues<double>());
                _dirty = false;
            }

            OnChanged(true, true, true, true, true);
        }
    }
}
